package controller

import (
	"fmt"
	"log/slog"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/docker/docker/api/types/network"
	logic2 "github.com/donknap/dpanel/app/application/logic"
	"github.com/donknap/dpanel/app/common/logic"
	"github.com/donknap/dpanel/app/common/logic/baota"
	"github.com/donknap/dpanel/app/common/logic/onepanel"
	"github.com/donknap/dpanel/common/accessor"
	"github.com/donknap/dpanel/common/dao"
	"github.com/donknap/dpanel/common/entity"
	"github.com/donknap/dpanel/common/function"
	"github.com/donknap/dpanel/common/service/compose"
	"github.com/donknap/dpanel/common/service/docker"
	"github.com/donknap/dpanel/common/service/docker/types"
	"github.com/donknap/dpanel/common/service/exec/local"
	"github.com/donknap/dpanel/common/service/notice"
	"github.com/donknap/dpanel/common/service/storage"
	"github.com/donknap/dpanel/common/types/define"
	"github.com/donknap/dpanel/common/types/event"
	"github.com/gin-gonic/gin"
	"github.com/we7coreteam/w7-rangine-go/v2/pkg/support/facade"
	"github.com/we7coreteam/w7-rangine-go/v2/src/http/controller"
)

type Store struct {
	controller.Abstract
}

func (self Store) Create(http *gin.Context) {
	type ParamsValidate struct {
		Id    int32                   `json:"id"`
		Title string                  `json:"title" binding:"required"`
		Type  string                  `json:"type" binding:"required"`
		Name  string                  `json:"name" binding:"required"`
		Url   string                  `json:"url" binding:"required"`
		Apps  []accessor.StoreAppItem `json:"apps" binding:"required"`
	}
	params := ParamsValidate{}
	if !self.Validate(http, &params) {
		return
	}

	if params.Id <= 0 {
		storeRow, _ := dao.Store.Where(dao.Store.Name.Eq(params.Name)).First()
		if storeRow != nil {
			self.JsonResponseWithError(http, function.ErrorMessage(define.ErrorMessageCommonIdAlreadyExists, "name", params.Name), 500)
			return
		}
	} else {
		storeRow, _ := dao.Store.Where(dao.Store.ID.Eq(params.Id)).First()
		if storeRow == nil {
			self.JsonResponseWithError(http, function.ErrorMessage(define.ErrorMessageCommonDataNotFoundOrDeleted), 500)
			return
		}
	}

	storeNew := &entity.Store{
		Title: params.Title,
		Name:  params.Name,
		Setting: &accessor.StoreSettingOption{
			Type:      params.Type,
			Url:       params.Url,
			UpdatedAt: time.Now().Unix(),
			Apps:      params.Apps,
		},
	}
	var err error
	if params.Id <= 0 {
		err = dao.Store.Create(storeNew)
	} else {
		_, err = dao.Store.Where(dao.Store.ID.Eq(params.Id)).Updates(storeNew)
		storeNew.ID = params.Id
	}

	if err != nil {
		self.JsonResponseWithError(http, err, 500)
		return
	}

	self.JsonSuccessResponse(http)
	return
}

func (self Store) Delete(http *gin.Context) {
	type ParamsValidate struct {
		Id []int32 `json:"id" binding:"required"`
	}
	params := ParamsValidate{}
	if !self.Validate(http, &params) {
		return
	}

	for _, id := range params.Id {
		storeRow, _ := dao.Store.Where(dao.Store.ID.Eq(id)).First()
		if storeRow == nil {
			self.JsonResponseWithError(http, function.ErrorMessage(define.ErrorMessageCommonDataNotFoundOrDeleted), 500)
			return
		}
		err := os.RemoveAll(filepath.Join(storage.Local{}.GetStorePath(), storeRow.Name))
		if err != nil {
			self.JsonResponseWithError(http, err, 500)
			return
		}
		_, err = dao.Store.Where(dao.Store.ID.Eq(id)).Delete()
		if err != nil {
			self.JsonResponseWithError(http, err, 500)
			return
		}

		facade.GetEvent().Publish(event.StoreDeleteEvent, event.StorePayload{
			Store: storeRow,
			Ctx:   http,
		})
	}

	self.JsonSuccessResponse(http)
	return
}

func (self Store) GetList(http *gin.Context) {
	type ParamsValidate struct {
		Title string `json:"title"`
		Name  string `json:"name"`
	}
	params := ParamsValidate{}
	if !self.Validate(http, &params) {
		return
	}

	var list []*entity.Store

	query := dao.Store.Order(dao.Store.ID.Desc())
	if params.Title != "" {
		query = query.Where(dao.Store.Title.Like("%" + params.Title + "%"))
	}
	if params.Name != "" {
		query = query.Where(dao.Store.Name.Like("%" + params.Name + "%"))
	}
	list, _ = query.Find()

	// 如果是本地商店，同步一遍数据
	for _, item := range list {
		if item.Setting.Type == define.StoreTypeOnePanelLocal {
			if appList, err := (logic.Store{}).GetAppByOnePanel(item.Name); err == nil {
				item.Setting.Apps = appList
				_ = dao.Store.Save(item)
			}
		}
	}

	self.JsonResponseWithoutError(http, gin.H{
		"list": list,
	})
	return
}

func (self Store) Sync(http *gin.Context) {
	type ParamsValidate struct {
		Id   int32  `json:"id"`
		Name string `json:"name" binding:"required"`
		Type string `json:"type" binding:"required"`
		Url  string `json:"url" binding:"required"`
	}
	params := ParamsValidate{}
	if !self.Validate(http, &params) {
		return
	}
	var err error

	storeRootPath := filepath.Join(storage.Local{}.GetStorePath(), params.Name)
	if _, err = os.Stat(storeRootPath); err != nil && params.Type == define.StoreTypeOnePanelLocal {
		_ = os.MkdirAll(filepath.Join(storeRootPath, "apps"), os.ModePerm)
	}

	appList := make([]accessor.StoreAppItem, 0)
	if params.Type == define.StoreTypeOnePanel || params.Type == define.StoreTypeOnePanelLocal {
		if params.Type == define.StoreTypeOnePanel {
			err = logic.Store{}.SyncByGit(params.Url, logic.SyncByGitOption{
				TargetPath: storeRootPath,
			})
			if err != nil {
				_ = notice.Message{}.Error(".gitPullEarlyEOF", "name", params.Name, "url", params.Url)
				self.JsonResponseWithError(http, err, 500)
				return
			}
		}
		appList, err = logic.Store{}.GetAppByOnePanel(params.Name)
		if err != nil {
			self.JsonResponseWithError(http, err, 500)
			return
		}
	} else if params.Type == define.StoreTypeCasaOs {
		err = logic.Store{}.SyncByZip(storeRootPath, params.Url, "Apps")
		if err != nil {
			self.JsonResponseWithError(http, err, 500)
			return
		}
		appList, err = logic.Store{}.GetAppByCasaos(params.Name)
		if err != nil {
			self.JsonResponseWithError(http, err, 500)
			return
		}
	} else if params.Type == define.StoreTypePortainer {
		jsonPath := filepath.Join(storeRootPath, "template.json")
		err = logic.Store{}.SyncByUrl(jsonPath, params.Url)
		if err != nil {
			self.JsonResponseWithError(http, err, 500)
			return
		}
	} else if params.Type == define.StoreTypeBaoTa {
		appList, err = logic.Store{}.GetAppByBaoTa(params.Name, params.Url)
		if err != nil {
			self.JsonResponseWithError(http, err, 500)
			return
		}
	}

	if params.Id > 0 {
		if storeRow, _ := dao.Store.Where(dao.Store.ID.Eq(params.Id)).First(); storeRow != nil {
			if len(appList) > 0 {
				storeRow.Setting.Apps = appList
			}
			storeRow.Setting.UpdatedAt = time.Now().Unix()
			_ = dao.Store.Save(storeRow)
		}
	}

	self.JsonResponseWithoutError(http, gin.H{
		"list": appList,
	})
	return
}

func (self Store) Deploy(http *gin.Context) {
	type ParamsValidate struct {
		StoreId     int32                        `json:"storeId" binding:"required"`
		TaskName    string                       `json:"taskName"`
		AppName     string                       `json:"appName"`
		AppTitle    string                       `json:"appTitle"`
		VersionInfo accessor.StoreAppVersionItem `json:"versionInfo"`
	}
	params := ParamsValidate{}
	if !self.Validate(http, &params) {
		return
	}

	storeRow, err := dao.Store.Where(dao.Store.ID.Eq(params.StoreId)).First()
	if storeRow == nil {
		slog.Debug("store deploy get store", "error", err)
		self.JsonResponseWithError(http, function.ErrorMessage(define.ErrorMessageCommonDataNotFoundOrDeleted), 500)
		return
	}

	composeYamlRealPath := filepath.Join(storage.Local{}.GetStorePath(), params.VersionInfo.ComposeFile)

	// 创建私有网络
	if v, ok := map[string]string{
		define.StoreTypeOnePanel:      define.StoreTypeOnePanelNetwork,
		define.StoreTypeBaoTa:         define.StoreTypeBaoTaNetwork,
		define.StoreTypeOnePanelLocal: define.StoreTypeOnePanelNetwork,
	}[storeRow.Setting.Type]; ok {
		if _, err := docker.Sdk.Client.NetworkInspect(docker.Sdk.Ctx, v, network.InspectOptions{}); err != nil {
			if _, err = docker.Sdk.Client.NetworkCreate(docker.Sdk.Ctx, v, network.CreateOptions{}); err != nil {
				self.JsonResponseWithError(http, err, 500)
				return
			}
		}
	}

	// 适配 1panel
	if storeRow.Setting.Type == define.StoreTypeOnePanel || storeRow.Setting.Type == define.StoreTypeOnePanelLocal {
		if v, ok := onepanel.DefaultEnv[params.AppName]; ok {
			params.VersionInfo.Environment = append(params.VersionInfo.Environment, v...)
		}
	}

	// 适配 bt
	if storeRow.Setting.Type == define.StoreTypeBaoTa {
		params.VersionInfo.Environment = function.PluckArrayWalk(params.VersionInfo.Environment, func(item types.EnvItem) (types.EnvItem, bool) {
			if find, _, ok := function.PluckMapItemWalk(baota.CommonEnv, func(k int, v types.EnvItem) bool {
				return v.Name == item.Name
			}); ok {
				item.Value = find.Value
				if item.Rule == nil {
					item.Rule = &types.EnvValueRule{}
				}
				item.Rule.Kind |= find.Rule.Kind
				item.Rule.Option = find.Rule.Option
			}
			return item, true
		})
		// 下载数据
		err := logic.Store{}.SyncByZip(filepath.Dir(filepath.Dir(composeYamlRealPath)), params.VersionInfo.Download, params.AppName)
		if err != nil {
			self.JsonResponseWithError(http, err, 500)
			return
		}
	}

	valueReplaceTable := function.NewReplacerTable(compose.ValueReplaceTable...)
	valueReplaceTable = append(valueReplaceTable, func(v *string) {
		*v = function.StringReplaceAll(*v, compose.PlaceholderAppTaskName, params.TaskName)
	})
	valueReplaceTable = append(valueReplaceTable, func(v *string) {
		*v = function.StringReplaceAll(*v, compose.PlaceholderAppName, params.AppName)
	})
	valueReplaceTable = append(valueReplaceTable, func(v *string) {
		*v = function.StringReplaceAll(*v, compose.PlaceholderAppVersion, params.VersionInfo.Name)
	})
	valueReplaceTable = append(valueReplaceTable, func(v *string) {
		if data, ok := http.Get("userInfo"); ok {
			if userInfo, ok := data.(logic.UserInfo); ok {
				*v = function.StringReplaceAll(*v, compose.PlaceholderCurrentUsername, userInfo.Username)
				return
			}
		}
	})
	function.Placeholder(&params.TaskName, valueReplaceTable...)

	envReplaceTable := function.NewReplacerTable(compose.EnvItemReplaceTable...)
	envReplaceTable = append(envReplaceTable, func(v *types.EnvItem) {
		function.Placeholder(&v.Value, valueReplaceTable...)
		return
	})
	for i, item := range params.VersionInfo.Environment {
		function.Placeholder(&item, envReplaceTable...)
		params.VersionInfo.Environment[i] = item
	}

	runComposeList := logic2.Compose{}.Ls()
	if _, _, ok := function.PluckArrayItemWalk(runComposeList, func(item *compose.ProjectResult) bool {
		return item.Name == params.TaskName
	}); ok {
		self.JsonResponseWithError(http, function.ErrorMessage(define.ErrorMessageCommonIdAlreadyExists, "name", params.TaskName), 500)
		return
	}

	composeNew := &entity.Compose{
		Name:  strings.ToLower(params.TaskName),
		Title: params.AppTitle,
		Setting: &accessor.ComposeSettingOption{
			Type:        accessor.ComposeTypeStore,
			Store:       fmt.Sprintf("%s:%s@%s@%s", storeRow.Setting.Type, storeRow.Title, storeRow.Setting.Url, params.AppName),
			Environment: params.VersionInfo.Environment,
			Uri: []string{
				filepath.Join(params.TaskName, filepath.Base(params.VersionInfo.ComposeFile)),
			},
			DockerEnvName: docker.Sdk.Name,
		},
	}

	targetPath := filepath.Join(storage.Local{}.GetComposePath(""), params.TaskName)
	if docker.Sdk.DockerEnv.EnableComposePath {
		targetPath = filepath.Join(storage.Local{}.GetComposePath(docker.Sdk.Name), params.TaskName)
	}

	err = dao.Compose.Create(composeNew)
	if err != nil {
		self.JsonResponseWithError(http, err, 500)
		return
	}

	err = function.CopyDir(targetPath, filepath.Dir(composeYamlRealPath))
	if err != nil {
		self.JsonResponseWithError(http, err, 500)
		return
	}

	// 适配 1panel
	if storeRow.Setting.Type == define.StoreTypeOnePanel || storeRow.Setting.Type == define.StoreTypeOnePanelLocal {
		initScript := filepath.Join(targetPath, "scripts", "init.sh")
		if _, err := os.Stat(initScript); err == nil {
			if cmd, err := local.New(
				local.WithCommandName("sh"),
				local.WithArgs(initScript),
				local.WithDir(targetPath),
			); err == nil {
				err = cmd.Run()
				if err != nil {
					slog.Debug("store deploy run init.sh", "error", err)
				}
			}
		}
	}

	facade.GetEvent().Publish(event.ComposeCreateEvent, event.ComposePayload{
		Compose: composeNew,
		Ctx:     http,
	})

	self.JsonResponseWithoutError(http, gin.H{
		"id": composeNew.ID,
	})
	return
}
